#include "mapgen.hpp"
#include <queue>
#include <cstdlib>
#include <iostream>
#include <ctime>
#include <fstream>
#include <cmath>

sPoint DIR[4] = {sPoint(0, -1), sPoint(1, 0), sPoint(0, 1), sPoint(-1, 0)};

cMapGen::cMapGen(int width, int height, int percent, int t)
{
    type = t;
    w = width;
    h = height;
    wallLikely = percent;

    biom = new sTemplateBiom;
    load_biom(type);

    init();
}

void cMapGen::texture()
{
    M = new int*[w];
    M_G = new int*[w];

    //czyscimy mape (-1)
    for (int i = 0; i < w; i++)
    {
        M[i] = new int[h];
        M_G[i] = new int[h];
        for (int j = 0; j < h; j++)
        {
            M[i][j] = -1;
            M_G[i][j] = -1;
        }
    }

    //liczymy ilosc kafli nie scian
    int free_fields = countFreeFields();

    //wyliczamy ilosc kafli kazdego rodzaju
    int tile_count[TILE_COUNT];

    for (int i = 0; i < TILE_COUNT; i++)
    {
        tile_count[i] = round(biom->tile_percent[i] * free_fields / 100);
    }

    //losujemy miejsce i wypelniamy je losową ilością losowego tila ;]
    int rand_pos_x = 0, rand_pos_y = 0, rand_tile = 0, rand_value = 0, p = 0, ret;

    int * rand_queue = randQueue();

    //dopoki mamy jakies wolne tile
    while (free_fields > 0)
    {
        //losujemy pozycje
        randPos(rand_pos_x, rand_pos_y);

        //losujemy klocka
        rand_tile = rand_queue[p];

        //jesli jego ilosc to zero to przechodzimy dalej...
        if (tile_count[rand_tile] == 0) {p++; if (p == TILE_COUNT) p = 0; continue;}

        //losujemy wartosc
        rand_value = rand() % tile_count[rand_tile];

        //kolejny klocek
        p++;
        if (p == TILE_COUNT) p = 0;

        //odejmujemy od wolnych pol rand_value
        free_fields -= rand_value;

        //wypelniamy mape
        ret = fillMap(M, rand_pos_x, rand_pos_y, rand_tile, rand_value);

        //zwracamy odpowiednie wartosci
        free_fields += ret;
    }

    for (int i = 0; i < w; i++)
        for (int j = 0; j < h; j++)
            if (map[i][j]) M_G[i][j] = 0;
}

void cMapGen::setParams(std::vector <int> live, std::vector <int> dead, int count)
{
    isDead = dead;
    isLive = live;
    repeat = count;
}

void cMapGen::init()
{
    srand(time(0));

    map = new bool*[w];
    tmp = new bool*[w];

    for (int i = 0; i < w; i++)
    {
        map[i] = new bool[h];
        tmp[i] = new bool[h];
    }

    for (int i = 0; i < w; i++)
        for (int j = 0; j < h; j++)
        {
            map[i][j] = randTile();
            tmp[i][j] = 1;
        }

    for (int i = 0; i < w; i++)
    {
        map[i][0] = 1;
        map[i][h-1] = 1;
    }

    for (int i = 0; i < h; i++)
    {
        map[0][i] = 1;
        map[w-1][i] = 1;
    }
}

bool cMapGen::randTile()
{
    if ((rand() %  100) < wallLikely)
        return true;
    else
        return false;
}

void cMapGen::step()
{
    int moor;
    bool live;

    for (int i = 1; i < w-1; i++)
    {
        for (int j = 1; j < h-1; j++)
        {
            //liczenie sasiadow
            moor = 0;
            for (int a = 0; a < 8; a++)
            {
                if (map[i + (int)MOOR[a].x][j + (int)MOOR[a].y]) moor++;
            }

            //sprawdzanie regul
            live = false;
            if (map[i][j])//jesli jest zywa
            {
                for (int a = 0; a < isLive.size(); a++)
                {
                    if (isLive[a] == moor) {live = true; break;}
                }
            }
            else
            {
                for (int a = 0; a < isDead.size(); a++)
                {
                    if (isDead[a] == moor) {live = true; break;}
                }
            }

            tmp[i][j] = live;
        }
    }

    //przerzucenie tmp do map
    for (int i = 0; i < h; i++)
        for (int j = 0; j < w; j++)
            map[j][i] = tmp[j][i];
}

void cMapGen::generate()
{
    for (int i = 0; i < repeat; i++)
        step();
}

void cMapGen::draw()
{
    std::cout << "\n";

    for (int j = 0; j < h; j++)
    {
        for (int i = 0; i < w; i++)
            if (map[i][j]) std::cout << "#";
            else std::cout << ".";
        std::cout << "\n";
    }

    std::cout << "\n";
}

void cMapGen::load_biom(int type)
{
    std::fstream file;
    std::string name("Data\\Bioms\\");
    name += getBiomNameByID(type);
    name += ".bio";

    file.open(name.c_str(), std::ios::in);
    if (!file) return;

    for (int i = 0; i < TILE_COUNT; i++)
        file >> biom->tile_percent[i];

    file.close();
}

const char * cMapGen::getBiomNameByID(int ID)
{
    switch (ID)
    {
        case MAP_TYPE_FOREST: return "forest";
        case MAP_TYPE_GRASSLAND: return "grassland";
        case MAP_TYPE_JUNGLE: return "jungle";
        case MAP_TYPE_MOUNTAIN: return "mountain";
        case MAP_TYPE_SAND: return "sand";
        case MAP_TYPE_VILLAGE: return "village";
        case MAP_TYPE_SNOW: return "snow";
    }
}

int cMapGen::countFreeFields()
{
    int l = 0;

    for (int i = 0; i < w; i++)
    {
        for (int j = 0; j < h; j++)
        {
            if (map[i][j] == 0) l++;
        }
    }

    return l;
}

int * cMapGen::randQueue()
{
    int * q = new int[TILE_COUNT];
    int l = 0, t;
    bool a;

    while (l != TILE_COUNT)
    {
        a = true;
        t = rand() % TILE_COUNT;
        for (int i = 0; i < l; i++)
        {
            if (q[i] == t) {a = false; break;}
        }
        if (a)
        {
            q[l] = t;
            l++;
        }
    }

    return q;
}

void cMapGen::randPos(int &x, int &y)
{
    for (int i = 0; i < w; i++)
    {
        for (int j = 0; j < h; j++)
        {
            if (!map[i][j] && M[i][j] == -1)
            {
                x = i;
                y = j;
                return;
            }
        }
    }
}

int cMapGen::fillMap(int ** fMap, int x, int y, int tile, int v)
{
    int value = v;

    for (int i = 0; i < w; i++)
        for (int j = 0; j < h; j++)
            tmp[i][j] = false;

    std::queue <int> q_x;
    std::queue <int> q_y;

    int w_x, w_y, v_x, v_y;

    q_x.push(x);
    q_y.push(y);

    tmp[x][y] = true;
    fMap[x][y] = tile;
    value--;

    while (value > 0)
    {
        v_x = q_x.front();
        v_y = q_y.front();
        q_x.pop();
        q_y.pop();

        for (int i = 0; i < 4; i++)
        {
            if (rand() % 100 < 30) continue;
            w_x = v_x + DIR[i].x;
            w_y = v_y + DIR[i].y;

            if (w_x < 0 || w_y < 0 || w_x >= w || w_y >= h) continue;
            if (tmp[w_x][w_y]) continue;
            if (map[w_x][w_y]) continue;
            if (fMap[w_x][w_y] != -1) continue;

            tmp[w_x][w_y] = true;
            fMap[w_x][w_y] = tile;
            value--;
            q_x.push(w_x);
            q_y.push(w_y);
        }

        if (q_x.empty()) break;
    }

    return value;
}

int cMapGen::fillMap_flood(int x, int y, int tile, int &v)
{
    if (v == 0) return 0;

    if (map[x][y]) return v;
    if (M[x][y] != -1) return v;
    M[x][y] = tile;
    v--;

    fillMap_flood(x+1, y, tile, v);
    fillMap_flood(x-1, y, tile, v);
    fillMap_flood(x, y+1, tile, v);
    fillMap_flood(x, y-1, tile, v);
    fillMap_flood(x+1, y+1, tile, v);
    fillMap_flood(x+1, y-1, tile, v);
    fillMap_flood(x-1, y+1, tile, v);
    fillMap_flood(x-1, y-1, tile, v);

    return v;
}

void cMapGen::linkHoles()
{
    //nasze dziury
    std::vector <sPoint> hole;
    std::vector <int> r;
    int t;

    //liczymy wolne pola na mapie
    int free_fields = countFreeFields();

    //czyscimy tymczasowa tablice
    holes = new int*[w];
    for (int i = 0; i < w; i++)
    {
        holes[i] = new int[h];
        for (int j = 0; j < h; j++)
        {
            holes[i][j] = -1;
            tmp[i][j] = false;
        }
    }

    int k = 0;

    for (int i = 0; i < w; i++)
        for (int j = 0; j < h; j++)
        {
            t = countTilesBFS(i, j, k);
            if (t != 0)
            {
                k++;
                hole.push_back(sPoint(i, j));
                r.push_back(t);
            }
        }

    for (int i = 0; i < h; i++)
    {
        for (int j = 0; j < w; j++)
            std::cout << char(holes[j][i] + 'a');
        std::cout << "\n";
    }

    /*//obliczamy odleglosc od kazdego pola do kazdego

    std::vector <sHole> s;
    std::vector <sHole> m;

    int tk, tl;

    for (int i = 0; i < w; i++)
    {
        for (int j = 0; j < h; j++)
        {
            if (holes[i][j] != -1)//jesli jest jakims holem
            {
                s.clear();
                for (int k = 0; k < w; k++)
                {
                    for (int l = 0; l < h; l++)
                    {
                        if (holes[k][l] != -1 && holes[k][l] != holes[i][j])
                        {
                            s.push_back(sHole(i, j, k, l, disBetweenPoint(i, j, k, l), holes[i][k]));
                        }
                    }
                }

                m.push_back(minimum(s));
            }
        }
    }

    std::vector <sHole> * qaz = new std::vector <sHole>[hole.size()];

    for (int i = 0; i < m.size(); i++)
    {
        qaz[m[i].h].push_back(m[i]);
    }

    /*sHole * q = new float[hole.size()];
    for (int i = 0; i < hole.size(); i++)
    {
        q[i] = minimum(qaz[i]);
    }*/

    //delete  qaz;

    /*for (int i = 0; i < hole.size(); i++)
    {
        sHole H = minimum(qaz[i]);
        float angle = countAngle(H.x1 - H.x2, H.y1 - H.y2);
        std::cout << angle;

        float xx = H.x1, yy = H.y1;

        while (xx != H.x2 && yy != H.y2)
        {
            xx += cos(angle) * 1.0f;
            yy += sin(angle) * 1.0f;
            map[int(xx)][int(yy)] = false;
        }
    }*/

    bool change;

    do
    {
        change = false;

        if (hole.size() == 1) break;

        for (int i = 0; i < hole.size(); i++)
        {
            if (r[i] < 30)
            {
                //int model = holes[int(hole[i].x)][int(hole[i].y)];
                for (int j = 0; j < w; j++)
                {
                    for (int k = 0; k < h; k++)
                    {
                        if (holes[j][k] == i)
                        {
                            map[j][k] = true;
                            holes[j][k] = -1;
                        }
                    }
                }

                hole.erase(hole.begin() + i);
                change = true;
                break;
            }

            std::vector <sPoint> tun = findWayToOtherHole(hole[i]);
            doTunnel(tun);
        }
    } while (change);

}

int cMapGen::countTilesBFS(int x, int y, int k)
{
    if (map[x][y] || tmp[x][y]) return 0;

    int l = 0;

    std::queue <sPoint> Q;
    sPoint v(0, 0), z(0, 0);

    Q.push(sPoint(x, y));
    tmp[x][y] = true;
    holes[x][y] = k;
    l++;

    while (!Q.empty())
    {
        v = Q.front();
        Q.pop();

        for (int i = 0; i < 8; i++)
        {
            z = v + MOOR[i];

            if (z.x < 1 || z.y < 1 || z.x >= w - 1 || z.y >= h - 1) continue;
            if (map[int(z.x)][int(z.y)]) continue;
            if (tmp[int(z.x)][int(z.y)]) continue;

            tmp[int(z.x)][int(z.y)] = true;
            holes[int(z.x)][int(z.y)] = k;
            l++;
            Q.push(z);
        }
    }

    return l;
}

std::vector <sPoint> cMapGen::findWayToOtherHole(sPoint s)
{
    int ** way = new int*[w];

    for (int i = 0; i < w; i++)
    {
        way[i] = new int[h];
        for (int j = 0; j < h; j++)
        {
            way[i][j] = -1;
        }
    }

    std::queue <sPoint> Q;
    sPoint v(0, 0), z(0, 0);

    Q.push(s);
    way[int(s.x)][int(s.y)] = 0;

    while (!Q.empty())
    {
        v = Q.front();
        Q.pop();

        for (int i = 0; i < 4; i++)
        {
            z = v + DIR[i];

            if (z.x < 1 || z.y < 1 || z.x >= w - 1 || z.y >= h - 1) continue;
            if (way[int(z.x)][int(z.y)] != -1) continue;

            way[int(z.x)][int(z.y)] = way[int(v.x)][int(v.y)] + 1;
            Q.push(z);

            if (holes[int(z.x)][int(z.y)] != -1 && holes[int(z.x)][int(z.y)] != holes[int(s.x)][int(s.y)]) break;
        }
        if (holes[int(z.x)][int(z.y)] != -1 && holes[int(z.x)][int(z.y)] != holes[int(s.x)][int(s.y)]) break;
    }

    std::vector <sPoint> ret;
    v = z;

    while (v != s)
    {
        ret.push_back(v);

        for (int i = 0; i < 4; i++)
        {
            z = v + DIR[i];

            if (way[int(z.x)][int(z.y)] == way[int(v.x)][int(v.y)] - 1)
            {
                v = z;
                break;
            }
        }
    }

    return ret;
}

void cMapGen::doTunnel(std::vector <sPoint> way)
{
    for (int i = 0; i < way.size(); i++)
    {
        map[int(way[i].x)][int(way[i].y)] = false;
        if ((rand() % 100) > 50) map[int(way[i].x)+1][int(way[i].y)] = false;
        if ((rand() % 100) > 50) map[int(way[i].x)][int(way[i].y)+1] = false;
        if ((rand() % 100) > 20) map[int(way[i].x)][int(way[i].y)+2] = false;
        if ((rand() % 100) > 20) map[int(way[i].x+2)][int(way[i].y)] = false;
    }
}

float cMapGen::minimum(std::vector <float> v)
{
    float min = v[0];

    for (int i = 0; i < v.size(); i++)
    {
        if (v[i] < min) min = v[i];
    }

    return min;
}

sHole cMapGen::minimum(std::vector <sHole> v)
{
    sHole min = v[0];

    for (int i = 0; i < v.size(); i++)
    {
        if (v[i].s < min.s) min = v[i];
    }

    return min;
}























